feat(storage): add storage zone and file commands with S3 credentials#98
feat(storage): add storage zone and file commands with S3 credentials#98jamie-at-bunny wants to merge 8 commits into
Conversation
🦋 Changeset detectedLatest commit: 7819dfe The changes in this PR will be included in the next version bump. This PR includes changesets to release 6 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
|
|
||
| # Files: list, upload, download, delete (paths are relative to the zone root) | ||
| bunny storage file list my-zone | ||
| bunny storage file list my-zone images/ |
There was a problem hiding this comment.
when dir doesn't exist it says Directory is empty. maybe a more accurate error message would be nicer. the current one implies the dir exists but empty.
There was a problem hiding this comment.
I don't think we know either way here, unless something changed in the API recently. I discovered this while working on bunny-upload/file-manager. We could instead return No files found at "images/". The path may be empty or not exist though, wdyt?
| bunny storage file list my-zone images/ | ||
| bunny storage file upload my-zone ./photo.png --to images/ | ||
| bunny storage file upload my-zone ./photo.png --checksum --content-type image/png | ||
| bunny storage file download my-zone images/photo.png --out ./local.png |
There was a problem hiding this comment.
this doesn't work for me. it gets stuck on downloading and blocks the process.
There was a problem hiding this comment.
@amir-at-bunny can you check with the latest this works as you expect.
|
@codex review |
There was a problem hiding this comment.
Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 2eb4f25d96
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| ): string[] { | ||
| const primary = primaryCode?.toUpperCase(); | ||
| const normalized = regions | ||
| .map((region) => region.trim().toUpperCase()) |
There was a problem hiding this comment.
Split comma-separated replication regions
The new examples use --replication LA,SG, but the parser passes that as one array element, and this normalization only trims/uppercases each element before validating it. As a result LA,SG is checked as a single region code and rejected as unknown, so the documented creation/update form fails unless users repeat the flag; split each entry on commas before validation or change the documented syntax.
Useful? React with 👍 / 👎.
| }; | ||
| } | ||
|
|
||
| if (output === "json") { |
There was a problem hiding this comment.
Add the requested domain before returning JSON
When bunny storage zones add ... --domain <host> --output json is used, --domain already made the pull zone get created, but this JSON branch returns before the custom-domain block below runs. The command therefore reports success while silently skipping the explicit domain the user asked to attach; perform the non-interactive hostname add before this return and include its result/error in the JSON payload.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.
There was a problem hiding this comment.
Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.
|
@codex review |
|
To use Codex here, create a Codex account and connect to github. |
|
@greptile-apps re-review |
Greptile SummaryThis PR adds a comprehensive
Confidence Score: 5/5The change is safe to merge. All new commands follow the established patterns in the codebase, passwords are stripped before JSON output, and the shell-quoting implementation for S3 env credentials is correctly tested. The code is well-structured with no new defects found beyond what was already discussed in prior review threads. The maskSecret fix from the previous round has been correctly applied. The only new observations are a detached JSDoc comment in flow.ts and a double disk read when --checksum is used on upload — both cosmetic or minor performance concerns that don't affect correctness. No files require special attention. The orphaned JSDoc in core/hostnames/flow.ts and the double file read in file/upload.ts are the only remaining items worth a second look. Important Files Changed
Reviews (3): Last reviewed commit: "apply greptile feedback" | Re-trigger Greptile |
| }); | ||
| replicationRegions = picked ?? []; | ||
| } | ||
| const replicationCodes = replicationRegions | ||
| ? normalizeReplicationRegions(replicationRegions, mainRegion) | ||
| : []; | ||
|
|
||
| const spin = spinner("Creating storage zone..."); | ||
| spin.start(); | ||
| let created: StorageZoneModel | undefined; | ||
| try { | ||
| const { data } = await client.POST("/storagezone", { | ||
| body: { | ||
| Name: zoneName, | ||
| Region: mainRegion, | ||
| ReplicationRegions: replicationCodes.length ? replicationCodes : null, | ||
| }, | ||
| }); | ||
| created = data; |
There was a problem hiding this comment.
Unchecked API response on zone creation — also affects
remove.ts and update.ts
client.POST (openapi-fetch) never throws on HTTP error responses; it returns { data: undefined, error: ... }. When the API rejects the request (e.g. 409 name conflict, 400 invalid region), data is undefined, created stays undefined, and execution continues — ultimately printing "Created storage zone <name>." or, in JSON mode, returning { "Name": "<name>", "PullZone": null, "CustomDomain": null } with no error signal. Callers have no way to distinguish a failure from success.
The same silent-success pattern appears in zone/remove.ts (the client.DELETE response is completely discarded) and zone/update.ts (the client.POST response is discarded), so a failed delete or update also logs "Deleted/Updated storage zone X." as if it succeeded.
There was a problem hiding this comment.
I don't think this applies. We get {data,error} in our responses, and no throws.
There was a problem hiding this comment.
Looking at the actual code, the error from client.POST is never checked — only data is destructured:
const { data } = await client.POST("/storagezone", { ... });
created = data;If the API returns a 409 or 400, data is undefined and error carries the failure — but error is silently dropped here. Execution continues past the try/finally, hits const zoneId = created?.Id (which is undefined), and eventually logs "Created storage zone <name>." as if it succeeded.
The fix would be to destructure and check error:
| }); | |
| replicationRegions = picked ?? []; | |
| } | |
| const replicationCodes = replicationRegions | |
| ? normalizeReplicationRegions(replicationRegions, mainRegion) | |
| : []; | |
| const spin = spinner("Creating storage zone..."); | |
| spin.start(); | |
| let created: StorageZoneModel | undefined; | |
| try { | |
| const { data } = await client.POST("/storagezone", { | |
| body: { | |
| Name: zoneName, | |
| Region: mainRegion, | |
| ReplicationRegions: replicationCodes.length ? replicationCodes : null, | |
| }, | |
| }); | |
| created = data; | |
| const { data, error } = await client.POST("/storagezone", { | |
| body: { | |
| Name: zoneName, | |
| Region: mainRegion, | |
| ReplicationRegions: replicationCodes.length ? replicationCodes : null, | |
| }, | |
| }); | |
| if (error) throw new UserError(String(error)); | |
| created = data; |
Tip: You can customize Greptile's behavior for this repo with .greptile/rules.md and .greptile/config.json.
|
We should also prompt before adding a replica that it cannot later be removed. |
No description provided.